//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/unicode/GMPUnicode.h"
#include "sagittarius/gmp/gmpsupport.h"
#include <assert.h>
#include <malloc.h>
#include <string.h>

///Convert buffer
int UTF8_GMPConvertBuffer(const char* in, GMPUnicodeChar* out)
{
    int total=0;
    int n, i;
    
    while(*in != 0x00)
    {
        if(*in & 0x80)
        {
            //more than one byte
            for(n = 2; n<=4; ++n)
            {
                if(!((*in << n) & 0x80))
                    break;
            }
            if((*in << n) & 0x80)
            {
                fprintf(stderr, "UTF8_GMPConvertBuffer: Unrecognized start byte (possibly encoding for forbidden five or six-byte sequence)\n");
                AN(0);
                return -1;
            }
            sg_bignum_set_ui(out, *in & ((0x01 << (7 - n)) - 1));
            AN(out->v);
            for(i=1; i!=n; ++i)
            {
                ++in;
                //continuation byte
                if((*in & 0xC0) != 0x80)
                {
                    fprintf(stderr, "UTF8_GMPConvertBuffer: Expected continuation byte\n");
                    return -1;
                }
                //get data
                sg_bignum_set_ui(out, (SG_GET_BIGNUM_UI(*out) << 6) | (*in & 0x3F));
            }
            ++in;
            ++out;
            ++total;
        }
        else
        {
            //one byte
            sg_bignum_set_ui(out, *in);
            ++out;
            ++in;
            ++total;
        }
    }
    //at this point, *in is 0x00, so add null-terminating character to output buffer
    sg_bignum_set_ui(out, 0x00);
    return total;
}

//convert to string
int UCGMPEncode_UTF8(GMPUnicodeChar* buf, char* out)
{
    size_t n_bytes;
    
    while(SG_BIGNUM_CMP_UI(*buf, 0x00))
    {
        n_bytes = UCGMPgetNBytes(*buf);
        switch(n_bytes)
        {
            case 1:
                *out = (char)(SG_GET_BIGNUM_UI(*buf) & 0x7F);
                ++out;
                ++buf;
                break;
            case 2:
                *out = (char)((SG_GET_BIGNUM_UI(*buf) >> 6) & 0x1F) | 0xC0;
                ++out;
                *out = (char)(SG_GET_BIGNUM_UI(*buf) & 0x3F) | 0x80;
                ++out;
                ++buf;
                break;
            case 3:
                *out = (char)((SG_GET_BIGNUM_UI(*buf) >> 12) & 0x0F) | 0xE0;
                ++out;
                *out = (char)((SG_GET_BIGNUM_UI(*buf) >> 6) & 0x3F) | 0x80;
                ++out;
                *out = (char)(SG_GET_BIGNUM_UI(*buf) & 0x3F) | 0x80;
                ++out;
                ++buf;
                break;
            case 4:
                *out = (char)((SG_GET_BIGNUM_UI(*buf) >> 18) & 0x07) | 0xF0;
                ++out;
                *out = (char)((SG_GET_BIGNUM_UI(*buf) >> 12) & 0x3F) | 0x80;
                ++out;
                *out = (char)((SG_GET_BIGNUM_UI(*buf) >> 6) & 0x3F) | 0x80;
                ++out;
                *out = (char)(SG_GET_BIGNUM_UI(*buf) & 0x3F) | 0x80;
                ++out;
                ++buf;
                break;
            default:
                assert(0 && "printUnicodeBuf: Invalid number of bytes");
        }
    }
    //null-terminating character
    *out = 0x00;
    return 0;
}

///UTF8_ShrinkBuffer
GMPUnicodeChar* UCGMPShrinkBuffer(GMPUnicodeChar* buf, size_t new_size)
{
    GMPUnicodeChar* newbuf;
    newbuf = (GMPUnicodeChar*)malloc(new_size*sizeof(GMPUnicodeChar));
    assert(newbuf != NULL);
    gmpcopy(newbuf, buf, new_size);
    free(buf);
    return newbuf;
}

///UCgetNBytes
int UCGMPgetNBytes(GMPUnicodeChar chr)
{
    //printf("UCgetNBytes: char = 0x%x\n", chr);
    if(SG_GET_BIGNUM_UI(chr) <= 0x007F)
        return 1;
    else if(SG_GET_BIGNUM_UI(chr) <= 0x07FF)
        return 2;
    else if(SG_GET_BIGNUM_UI(chr) <= 0xFFFF)
        return 3;
    else if(SG_GET_BIGNUM_UI(chr) <= 0x1FFFFF)
        return 4;
    else if(SG_GET_BIGNUM_UI(chr) <= 0x3FFFFFF)
        return 5;
    else if(SG_GET_BIGNUM_UI(chr) <= 0x7FFFFFFF)
        return 6;
    else
        assert(0 && "UCgetNBytes: Unrecognized codepoint");
}

//get buffer length
size_t UCGMP_UTF8BufferLen(GMPUnicodeChar* buf)
{
    size_t n_bytes;
    n_bytes = 0;
    while(SG_BIGNUM_CMP_UI(*buf, 0x00))
    {
        n_bytes += UCGMPgetNBytes(*buf);
        ++buf;
    }
    ++n_bytes; //get null-terminating character
    return n_bytes;
}

//get buffer length
size_t UCGMPBufferLen(GMPUnicodeChar* buf)
{
    size_t n;
    n = 0;
    while(SG_BIGNUM_CMP_UI(*buf, 0x00))
    {
        ++n;
        ++buf;
    }
    ++n; //get null-terminating character
    return n;
}
